package com.sky.pay.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.sky.pay.api.request.RefundRequest;
import com.sky.pay.api.request.WeixinPayOrderRequest;
import com.sky.pay.common.*;
import com.sky.pay.config.WeixinPayConfig;
import com.sky.pay.service.OrderService;
import com.sky.pay.service.PayService;
import com.sky.pay.service.RefundService;
import com.sky.pay.service.WeixinPayService;
import com.sky.pay.vo.Order;
import com.sky.pay.vo.Refund;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author yangsk
 */
@Service
@Slf4j
public class WeixinPayServiceImpl implements WeixinPayService {

    @Autowired
    private WeixinPayConfig weixinPayConfig;
    @Autowired
    private OrderService orderService;

    @Autowired
    private PayService payService;

    @Autowired
    private RefundService refundService;


    /**
     * 1、生成服务自己的订单
     * 2、组装数据向微信发起下单请求
     * @param request
     * @return
     */
    @Override
    public Map<String, Object> nativePay(WeixinPayOrderRequest request) throws Exception {
        Map<String,Object> resultMap = null;
        log.info("商家生成商品订单");
        /**
         * 用户点击支付，商家生成用户订单并向微信发起统一下单。并接收微信生成的付款二维码
         * 商户下单成功后，将付款二维码返回前端页面并弹出，但是这个时候用户没有扫码支付，而是将付款二维码关闭了，
         * 当用户再次点击支付的时候，商家应该先检查用户订单是否存在，如果用户订单存且付款二维码也存在。
         * 直接返回付款二维码给前端页面并弹出，用户扫码支付。
         */
        Order order = orderService.getExistOrder(request);
        if(ObjectUtils.isEmpty(order)){
            order = orderService.createOrder(request);
        }
        if(!ObjectUtils.isEmpty(order) && !StringUtils.isEmpty(order.getCodeUrl())){
            resultMap.put("codeUrl",order.getCodeUrl());
            resultMap.put("orderNo",order.getOrderNo());
            return resultMap;
        }

        log.info("商户调用微信的统一下单接口");
        String payUrl = weixinPayConfig.getDomain().concat(WeixinRouter.WEIXIN_CRATE_ORDER_URL.getType());

        //组装数据向微信发起付款交易
        HttpPost httpPost = new HttpPost(payUrl);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        //组装统一下单请求数据
        ObjectNode rootNode = createObjectNode(request,order,objectMapper);

        String bodyAsString = "";//交易结果
        CloseableHttpResponse response = null;

        try{
            log.info("商家组装好微信交易付款单的请求数据后向微信发起支付请求的参数对象:{}",rootNode);
            objectMapper.writeValue(bos, rootNode);
            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            response = weixinPayConfig.getWinxinClient().execute(httpPost);
            bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println(bodyAsString);
            //将结果转成json
            JSONObject parseJson = (JSONObject)JSON.parse(bodyAsString);
            Integer status = parseJson.getInteger("status");
            if(200 == status){
                log.info("商户向微信发起的下单支付交易成功");
                orderService.saveOrderPayCodeUrl(order.getOrderNo(),parseJson.get("code_url").toString());
                //解析结果数据,codeUrl的有效时间是2小时
                resultMap.put("codeUrl",parseJson.get("code_url"));
                resultMap.put("orderNo",order.getOrderNo());
            }

        }catch (Exception e){
            log.info("商家组装好微信交易付款单的请求数据后向微信发起支付请求,交易失败的参数对象:{}",rootNode);
            e.printStackTrace();
        }finally {
            response.close();
        }

        return resultMap;
    }
    /*
     * "resource": {
     *  "original_type": "transaction",
     *  "algorithm": "AEAD_AES_256_GCM",
     *  "ciphertext": "",
     *  "associated_data": "",
     *  "nonce": ""
     *  }
     */
    @Override
    public String decryptFromBodyResource(HashMap<String, Object> resultBody) throws UnsupportedEncodingException, GeneralSecurityException {
        //微信自带的解密工具
        AesUtil aesUtil = new AesUtil(weixinPayConfig.getApiV3Key().getBytes("UTF-8"));
        Map<String,Object> resoureMap = (HashMap)resultBody.get("resouce");
        String associatedData = (String)resoureMap.get("associated_data");
        String nonce = (String)resoureMap.get("nonce");
        String ciphertext = (String)resoureMap.get("ciphertext");
        //调用decryptToString方法对数据进行解密
        return  aesUtil.decryptToString(associatedData.getBytes("UTF-8"), nonce.getBytes("UTF-8"), ciphertext);
    }
     /*
     * @param plainText
     */
    @Override
    public void createTradeRecord(String plainText) {
        //将字符串转成map，或者也可以将其转为对象,看资格需求
        HashMap<String,Object> hashMap = JSONObject.parseObject(plainText, HashMap.class);
        String outTradeNo = (String)hashMap.get("out_trade_no");
        //更新客户订单：未支付-->支付成功
        orderService.updateOrderStatus(outTradeNo,OrderStatus.SUCCESS.toString());
        //生成商家和微信的交易记录
        payService.createPayRecord(plainText,outTradeNo);

    }

    @Override
    public void cancleOrder(String orderNo) throws Exception {
        /**
         * 1.向微信发送请求，关闭之前下单成功后，在微信那边生成的交易单
         * 2.关闭用户订单
         */
        this.closeWeixinPayOrder(orderNo);
        orderService.updateOrderStatus(orderNo, OrderStatus.CANCEL.toString());
    }

    @Override
    public String queryOrder(String orderNo) throws IOException {
        String queryWeixinOrderUrl = String.format(WeixinRouter.WEIXIN_ORDER_QUERY_URL.getType(),orderNo);
        queryWeixinOrderUrl = weixinPayConfig.getDomain()
                .concat(queryWeixinOrderUrl)
                .concat("?mchid="+weixinPayConfig.getMerchantId());

        HttpGet httpget = new HttpGet(queryWeixinOrderUrl);
        httpget.setHeader("Accept", "application/json");

        CloseableHttpResponse response = null;
        String bodyAsString ="";
        try{
            //完成reqeust对象的签名，向微信发送请求
            response = weixinPayConfig.getWinxinClient().execute(httpget);

            bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println(bodyAsString);

            if(200 == response.getStatusLine().getStatusCode()){
                log.info("商家查询微信支付单成功");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            response.close();
        }
        return bodyAsString;
    }


    /**
     * 获取微信交易单状态，核实订单状态，
     * 当交易单已支付，则更新用户的订单状态,生成商家和微信的交易记录
     * 当交易单未支付，则调用微信关单接口关闭交易单，更新用户订单状态
     * @param orderNo
     */
    @Override
    public void checkWeixinPayOrderStatus(String orderNo) throws Exception {
        String weixinOrderStatusResult = this.queryOrder(orderNo);
        HashMap hashMap = JSONObject.parseObject(weixinOrderStatusResult, HashMap.class);
        String tradeOrderState = (String)hashMap.get("trade_statue");
        //支付成功
        if(WeixinTradeState.SUCCESS.equals(tradeOrderState)) {
            //更新用户订单状态，生成商家和微信的交易记录单
            orderService.updateOrderStatus(orderNo, OrderStatus.SUCCESS.toString());
            payService.createPayRecord(weixinOrderStatusResult, orderNo);
        }
        //未支付,向微信发送关单请求关闭交易单，同时关闭用户单
        if(WeixinTradeState.NOPAY.equals(tradeOrderState)){
            this.closeWeixinPayOrder(orderNo);
            orderService.updateOrderStatus(orderNo,OrderStatus.CLOUSED.toString());
        }
    }

    public void refund(RefundRequest request) throws IOException {
        //商家生成退款单
        Refund refund = refundService.createRefundOrder(request);
        //微信退款申请url
        String refundUrl = String.format(WeixinRouter.WEIXIN_ORDER_REFUND_URL.getType());
        refundUrl = weixinPayConfig.getDomain().concat(refundUrl);

        HttpPost httpPost = new HttpPost(refundUrl);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        //组装统一下单请求数据
        ObjectNode rootNode = createRefundObjectNode(refund,objectMapper);

        String bodyAsString = "";//交易结果
        CloseableHttpResponse response = null;
        try{
            log.info("商家组装好微信微信退款单的数据后向微信发起退款申请请求的参数对象:{}",rootNode);
            objectMapper.writeValue(bos, rootNode);
            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            response = weixinPayConfig.getWinxinClient().execute(httpPost);
            bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println(bodyAsString);

            int status = response.getStatusLine().getStatusCode();
            if(200 == status){
                log.info("商户向微信发起的退款申请交易成功,更新订单状态，更新退款单状态");
                orderService.updateOrderStatus(refund.getOrderNo(),OrderStatus.REFUND_PROCESSING.getType());
                refundService.updateRefundOrder(bodyAsString);
            }else{
                log.error("商户向微信发起的退款申请交易失败");
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            response.close();
        }

    }

    @Override
    public String getQueryRefund(String refundNo) throws IOException {
        String queryWeixinOrderUrl = String.format(WeixinRouter.WEIXIN_ORDER_REFUND_QUERY_URL.getType(),refundNo);
        queryWeixinOrderUrl = weixinPayConfig.getDomain().concat(queryWeixinOrderUrl);


        HttpGet httpget = new HttpGet(queryWeixinOrderUrl);
        httpget.setHeader("Accept", "application/json");

        CloseableHttpResponse response = null;
        String bodyAsString ="";
        try{
            //完成reqeust对象的签名，向微信发送请求
            response = weixinPayConfig.getWinxinClient().execute(httpget);

            bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println(bodyAsString);

            if(200 == response.getStatusLine().getStatusCode()){
                log.info("商家查询微信退款单成功");
            }else{
                log.error("商家查询微信退款单失败");
                new RuntimeException("商家查询退款单异常",new Exception());
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            response.close();
        }
        return bodyAsString;
    }

    @Override
    public void checkWeixinRefundOrderStatus(String refundNo) throws IOException {
        String queryRefund = this.getQueryRefund(refundNo);
        HashMap hashMap = JSONObject.parseObject(queryRefund, HashMap.class);
        String refundOrderState = (String)hashMap.get("refund_statue");
        /**
         * 根据微信退款单的状态，处理商家退款单和订单
         */

    }

    private void closeWeixinPayOrder(String orderNo) throws Exception {
        //WEIXIN_ORDER_CLOUD_URL用到了占位符，所以要用到format函数对参进行替换,效果如下
        ///v3/pay/transactions/out-trade-no/%s/close
        ///v3/pay/transactions/out-trade-no/{orderNo}/close
        String clousePayOrderurl = String.format(WeixinRouter.WEIXIN_ORDER_CLOUD_URL.getType(),orderNo);
        clousePayOrderurl = weixinPayConfig.getDomain().concat(clousePayOrderurl);
        HttpPost httpPost = new HttpPost(clousePayOrderurl);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");

        //组装数据
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("mchid",weixinPayConfig.getMerchantId());
        httpPost.setEntity(new StringEntity(jsonObject.toString(),("UTF-8")));
        CloseableHttpResponse response = null;
        try{
            //完成reqeust对象的签名，向微信发送请求
            response = weixinPayConfig.getWinxinClient().execute(httpPost);
            if(200 == response.getStatusLine().getStatusCode()){
                log.info("关闭微信支付单成功");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            response.close();
        }
    }


    /**
     * 组装统一下单数据
     * @param request
     * @param order
     * @param objectMapper
     * @return
     */
    private ObjectNode createObjectNode(WeixinPayOrderRequest request,Order order,ObjectMapper objectMapper){
        //微信的回调地址
        String paySuccessNotifyUrl = weixinPayConfig.getDomain().concat(WeixinNotify.WEIXIN_ORDER_PAY_SUCCESS.toString());
        //交易金额
        Map amount = new HashMap<String,Object>();
        amount.put("totle",order.getTotleFee().intValue());
        amount.put("currency","CNY");

        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid",weixinPayConfig.getMerchantId())
                .put("appid", weixinPayConfig.getAppid())
                .put("description", order.getTitle())
                .put("notify_url", paySuccessNotifyUrl)
                .put("out_trade_no", order.getOrderNo());
        rootNode.putObject(amount.toString())
                .put("total", request.getProductTotal());
        rootNode.putObject("payer")
                .put("openid", request.getWeixinOpenId());
        return rootNode;
    }

    private ObjectNode createRefundObjectNode(Refund refund,ObjectMapper objectMapper){
        //微信退款成功后的回调地址通知地址
        String refundSuccessNotifyUrl = weixinPayConfig.getDomain().concat(WeixinNotify.WEIXIN_ORDER_REFUND_SUCCESS.getType());
        //退款金额
        Map amount = new HashMap<String,Object>();
        amount.put("refund",refund.getRefund().intValue());
        amount.put("total",refund.getTotleFee().intValue());
        amount.put("currency","CNY");


        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("out_refund_no",refund.getRefundNo())
                .put("reason", refund.getReason())
                .put("notify_url",refundSuccessNotifyUrl )
                .put("out_trade_no", refund.getOrderNo())
                .put("amount",amount.toString());
        return rootNode;
    }
}
